// client.c++
// client class implementation file

#include <iostream>
#include <SDL/SDL_net.h>
#include <assert.h>
#include <vector>

#include "client.h"
#include "ui.h"
#include "map.h"
#include "objects.h"

std::vector<ClientData*> clients;
extern UI::TextBox* console;
extern Map* map;
extern Objects objects;
extern unsigned int phase;
extern unsigned int round;

Client::Client() {
	isConnected_ = 0;
	socketSet_ = NULL;
	serverSocket_ = NULL;
	messageBuffer_ = NULL;
	messageBufferLen_ = 0;
	me_ = -1;
	wizard_ = 0;

	// initialise message handlers
	messageHandlers_["MAPSEND"] 		= &Client::getMap;
	messageHandlers_["ADDFOUNTAIN"]		= &Client::recvAddFountain;
	messageHandlers_["DISCONNECT"] 		= &Client::recvDisconnect;
	messageHandlers_["NEWCLIENTINFO"] 	= &Client::recvNewClientInfo;
	messageHandlers_["NEWWIZARD"] 		= &Client::recvNewWizard;
	messageHandlers_["CHAT"] 			= &Client::recvChat;
	messageHandlers_["YOU"] 			= &Client::recvYou;
	messageHandlers_["NEWTURN"]			= &Client::recvNewTurn;
	messageHandlers_["NEWPHASE"]		= &Client::recvNewPhase;
	messageHandlers_["NEWROUND"]		= &Client::recvNewRound;
	messageHandlers_["MOVE"] 			= &Client::recvMove;
}

Client::~Client() {
	disconnect();
	if (messageBuffer_ != NULL)
		free(messageBuffer_);
}

unsigned int Client::sendData(const void* data, const unsigned int bytes) {
	if (isConnected_ == 0)
		return 0;
	unsigned int sent = SDLNet_TCP_Send(serverSocket_, data, bytes);
	if (sent == bytes) {
		return sent;
	} else {
		disconnect();	
		return -1;
	}
}

// Watch it, this function returns -1 on epic fail
unsigned int Client::receiveData(void* data, const unsigned int maxLength,
                                 const unsigned int timeout) {
	assert(data != NULL);
	if (isConnected_ == 0)
		return 0;
	signed int nReady = SDLNet_CheckSockets(socketSet_, timeout);
	if (nReady == -1) {
		std::cout << "ERROR: SDLNet_CheckSockets: " << SDLNet_GetError()
		          << std::endl;
		perror("SDLNet_CheckSockets");
		exit(1);
	}
	if (nReady == 0) {
		return 0;
	} else {
		if (SDLNet_SocketReady(serverSocket_) != 0) {
			signed int dataReceived = SDLNet_TCP_Recv(serverSocket_, data,
			                                          maxLength);
			if (dataReceived < 0) {
				std::cout << "ERROR: SDLNet_TCP_Recv: " << SDLNet_GetError()
				          << std::endl;
				disconnect();
				return -1;
			}
			if (dataReceived != 0) {
				return dataReceived;
			} else {
				std::cout << "You have been disconnected from the server."
				          << std::endl;
				console->addText("You have been disconnected from the server.");
				disconnect();
				return -1;
			}
		}
	}
	// This wont happen.
	return 0;
}

bool Client::connect(const char* hostName, const unsigned int port) {
	IPaddress ip;
	isConnected_ = 1;
	if (SDLNet_ResolveHost(&ip, hostName, port) == -1) {
		std::cout << "ERROR: SDLNet_ResolveHost: " << SDLNet_GetError()
		          << std::endl;
		isConnected_ = 0;
	}
	serverSocket_ = SDLNet_TCP_Open(&ip);
	if (serverSocket_ == NULL) {
		std::cout << "ERROR: SDLNet_TCP_Open: " << SDLNet_GetError()
		          << std::endl;
		isConnected_ = 0;
	}
	socketSet_ = SDLNet_AllocSocketSet(1);
	if (socketSet_ == NULL) {
		std::cout << "ERROR: SDLNet_AllocSocketSet: " << SDLNet_GetError()
		          << std::endl;
		SDLNet_TCP_Close(serverSocket_);
		isConnected_ = 0;
	}
	if (SDLNet_TCP_AddSocket(socketSet_, serverSocket_) == -1) {
		std::cout << "ERROR: SDLNet_TCP_AddSocket: " << SDLNet_GetError()
		          << std::endl;
		SDLNet_TCP_Close(serverSocket_);
		SDLNet_FreeSocketSet(socketSet_);
		isConnected_ = 0;
	}
	return isConnected_;
}

void Client::disconnect() {
	if (isConnected_ == 0)
		return;
	std::cout << "Disconnecting from server.\n";
	if (SDLNet_TCP_DelSocket(socketSet_, serverSocket_) == -1)
		std::cout<<"ERROR: SDLNet_TCP_DelSocket: " << SDLNet_GetError()
				 << std::endl;
	SDLNet_TCP_Close(serverSocket_);
	SDLNet_FreeSocketSet(socketSet_);
	socketSet_ = NULL;
	serverSocket_ = NULL;
	isConnected_ = 0;
	if (messageBuffer_ != NULL)
		free(messageBuffer_);
	messageBuffer_ = NULL;
	messageBufferLen_ = 0;
}

#define BUFFER_SIZE 256

// This should be a latency-proof method for receiving messages from clients.
void Client::getMessages() {
	unsigned int received;
	if (isConnected_ == 0) 
		return;
	if (messageBuffer_ == NULL) {
		messageBuffer_ = (char*) malloc(BUFFER_SIZE);
		if (messageBuffer_ == NULL) {
			std::cout << "memory allocation failed.\n";
			exit(1);
		}
	}
	for (;; ++messageBufferLen_) {
		// Check to make sure the message won't overflow the buffer.
		// This will be handled better in the future.
		if (messageBufferLen_ >= BUFFER_SIZE) {
			std::cout<<"Message too long.\n";
			disconnect();
			return;
		}
		received = receiveData(&(messageBuffer_[messageBufferLen_]), 1, 1);

		// If no data was received, break, the current buffer is kept
		// so that it can continue to be filled again later.
		if (received == 0) {
				break;
		}
		// Connection error.
		if (received == (unsigned int)-1) {
				break;
		}
		// Is it the end of the message?
		if (messageBuffer_[messageBufferLen_] == '\0') {
			//std::cout << messageBuffer_ << "\n";
			std::cout << "Client: Message received: " << messageBuffer_ << "\n";
			parseMessage(messageBuffer_, BUFFER_SIZE, 0);
			free(messageBuffer_);
			messageBuffer_ = NULL;
			messageBufferLen_ = 0;
			break;
		}
	}
}

void Client::setMe(const unsigned int me) {
	me_ = me;
}

unsigned int Client::getMe() const {
	return me_;
}

void Client::parseMessage(char* message, const unsigned int bufferSize,
                          const unsigned int sender) {
	// tokenise the message
	std::vector<std::string> tokens;
	std::string currentToken;
	unsigned int tokenStart = 0;
	for (unsigned int i = 0; ; ++i) {
		if (message[i] == '|') {
			currentToken.assign(&message[tokenStart], i - tokenStart);
			tokens.push_back(currentToken);
			tokenStart = (i + 1);
		} else if (message[i] == '\0') {
			currentToken.assign(&message[tokenStart]);
			tokens.push_back(currentToken);
			break;
		}
	}
	
	// if there is no message handler, report invalid message
	if (messageHandlers_.count(tokens.at(0)) == 0) {
		invalidMessage(message, "No message handler", sender);
		return;
	}
	
	// this calls the relevant message handler member.
	// sorry about the syntax
	(this->*messageHandlers_[tokens.at(0)])(tokens, message, sender);
}

bool Client::sendMessage(const char* message) {
	std::cout << "Sending:" << message << "\n";
	if (sendData(message, strlen(message) + 1) <= 0) {
		std::cout << "Message sending to server failed.\n";
		return false;
	}
	return true;
}

void Client::invalidMessage(const char* message, const char* reason, 
                            const unsigned int sender) {
	std::cout << "Invalid message: " << message << "\n"
	          << reason << "\nsent by: " << sender <<"\n";
	disconnect();
}

// Message handlers ============================================================
void Client::getMap(std::vector<std::string>& tokens, const char* message,
                    const unsigned int sender) {
	unsigned int width = 0;
	unsigned int height = 0;
	unsigned int totalBytesReceived = 0;
	unsigned int bytes;
	unsigned int totalBytes;
	unsigned char tile;
	// Check for correct number of parameters
	if (tokens.size() != 3) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	width = atoi(tokens.at(1).c_str());
	height = atoi(tokens.at(2).c_str());
	totalBytes = width * height;
	// Sanity check parameters
	if (totalBytes > 65536) {
		invalidMessage(message, "Map too large", sender);
		return;
	}
	std::cout << "Downloading map. Dimensions: " << width << " x "
	          << height << "\n";
	map->uninitialise();
	map->setSize(width, height);
	map->initialise();
	
	while (totalBytesReceived < totalBytes) {
		bytes = receiveData(&tile, 1, 1000);
		if (bytes == (unsigned int)-1) {
			std::cout << "Map download failed.\n";
			disconnect();
			return;
		}
		else
			map->setTile(0, totalBytesReceived % width,
			             totalBytesReceived / width, tile);
			totalBytesReceived += bytes;
		if (totalBytesReceived % (totalBytes / 10) == 1) {
			std::cout << "\r" << totalBytesReceived << " bytes received. ["
			          << (totalBytesReceived / totalBytes) * 100 << "]%";
		}
	}
	std::cout << "\r" << totalBytesReceived<< " bytes received. ["
	          << (totalBytesReceived / totalBytes) * 100 << "%]\n";
	
	std::cout << "Map has been fully received.\n";
}

void Client::recvAddFountain(std::vector<std::string>& tokens,
                             const char* message, const unsigned int sender) {
	unsigned int x, y;
	// Check for correct number of parameters
	if (tokens.size() != 3) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	x = atoi(tokens.at(1).c_str());
	y = atoi(tokens.at(2).c_str());
	// Sanity check parameters
	
	if (x >= map->getWidth() || y >= map->getHeight()) { 
		invalidMessage(message, "Fountain outside of map.", sender); 
		return;
	}
	
	objects.addFountain(x, y);
}

void Client::recvNewWizard(std::vector<std::string>& tokens, const char* message,
                           const unsigned int sender) {
	unsigned int x, y, clientId;
	//Wizard **wizardPtr;
	// Check for correct number of parameters
	if (tokens.size() != 4) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	x = atoi(tokens.at(1).c_str());
	y = atoi(tokens.at(2).c_str());
	clientId = atoi(tokens.at(3).c_str());
	
	// Sanity check parameters
	
	if (x >= map->getWidth() || y >= map->getHeight()) { 
		invalidMessage(message, "New wizard outside of map.", sender); 
		return;
	}
	
	objects.addWizard(x, y, clientId);
}

void Client::recvNewClientInfo(std::vector<std::string>& tokens,
                               const char* message, const unsigned int sender) {
	unsigned int clientId, isConnected;
	char text[128];
	if (tokens.size() != 4) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	// Sanity check parameters
	clientId = atoi(tokens.at(1).c_str());
	isConnected = atoi(tokens.at(2).c_str());
	ClientData* clientData = new ClientData;
	clientData->isConnected = isConnected;
	clientData->name = new char[tokens.at(3).size() + 1];
	strcpy(clientData->name, tokens.at(3).c_str());
	clients.push_back(clientData);
	// only notify about new clients since we connected
	if (clientId > getMe()) {
		snprintf(text, 128, "%s has connected!\n", tokens.at(3).c_str());
		console->addText(text);
	}
}

void Client::recvDisconnect(std::vector<std::string>& tokens,
                            const char* message, const unsigned int sender) {
	// Check for correct number of parameters
	if (tokens.size() != 3) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	char text[128];
	unsigned clientId = atoi(tokens[1].c_str());
	
	if (clientId == getMe()) {
		snprintf(text, 128, "You have disconnected: %s\n",
		         tokens.at(2).c_str());
		std::cout << text;
		console->addText(text);
		disconnect();
	} else {
		snprintf(text, 128, "%s has disconnected: %s\n",
		         clients.at(clientId)->name, tokens.at(2).c_str());
		std::cout << text;
		console->addText(text);
	}
	clients.at(clientId)->isConnected = false;
}

void Client::recvChat(std::vector<std::string>& tokens, const char* message,
                      const unsigned int sender) {
	if (tokens.size() != 3) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	if (tokens.at(2).size() > 255) {
		invalidMessage(message, "Chat too long", sender);
	}
	// temporary
	char text[256];
	unsigned clientId = atoi(tokens.at(1).c_str());
	
	snprintf(text, 256, "%s: %s\n", clients.at(clientId)->name,
	         tokens.at(2).c_str());
	console->addText(text);
}

void Client::recvYou(std::vector<std::string>& tokens, const char* message,
                     const unsigned int sender) {
	unsigned me;
	me = atoi(tokens.at(1).c_str());
	setMe(me);
}

void Client::recvNewTurn(std::vector<std::string>& tokens, const char* message,
                         const unsigned int sender) {
	char text[128];
	if (tokens.size() != 2) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	unsigned int wizard = atoi(tokens.at(1).c_str());
	for (unsigned i = 0; i < objects.getNWizards(); ++i) {
		if (objects.getWizard(i)->getIsMyTurn()) {
			std::cout << i << "'s turn ends" << std::endl;
			snprintf(text, 128, "%s's turn ends.\n",
					clients.at(objects.getWizard(i)->getClientId())->name);
			console->addText(text);
			objects.getWizard(i)->setIsMyTurn(false);
			if (phase == PHASE_CASTING) {
				objects.getWizard(i)->setHasCast(true);
			} else if (phase == PHASE_MOVEMENT) {
				objects.getWizard(i)->setHasMoved(true);
			}
		}
	}
	std::cout << "its now " << wizard << "'s turn" << std::endl;
	snprintf(text, 128, "%s's turn begins...\n",
			clients.at(objects.getWizard(wizard)->getClientId())->name);
	console->addText(text);
	objects.getWizard(wizard)->setIsMyTurn(true);
}

void Client::recvNewPhase(std::vector<std::string>& tokens, const char* message,
                          const unsigned int sender) {
	if (tokens.size() != 2) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	unsigned int newPhase = atoi(tokens[1].c_str());
	if (newPhase == PHASE_CASTING) {
		std::cout << "Casting phase" << std::endl;
		console->addText("Casting phase begins...\n");
	} else if (newPhase == PHASE_MOVEMENT) {
		std::cout << "Movement phase" << std::endl;
		console->addText("Movement phase begins...\n");
	} else {
		invalidMessage(message, "Invalid phase", sender);
		return;
	}
	phase = newPhase;

}

void Client::recvNewRound(std::vector<std::string>& tokens, const char* message,
                          const unsigned int sender) {
	char text[128];
	if (tokens.size() != 1) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	++round;

	// TODO: other stuff like flood etc here !!!!!!!!!!
	for (unsigned i = 0; i < objects.getNWizards(); ++i) {
		objects.getWizard(i)->setHasCast(false);
		objects.getWizard(i)->setHasMoved(false);
	}
	std::cout << "New round:" << round << std::endl;
	snprintf(text, 128, "Round %u begins...\n", round);
	console->addText(text);

}

void Client::recvMove(std::vector<std::string>& tokens, const char* message,
                      const unsigned int sender) {
	if (tokens.size() != 4) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	unsigned int wizard = atoi(tokens.at(1).c_str());
	unsigned int x = atoi(tokens.at(2).c_str());
	unsigned int y = atoi(tokens.at(3).c_str());

	if ((x < 0) || (x >= map->getWidth()) 
			|| (y < 0) || (y >= map->getHeight())) {
		invalidMessage(message, "cannot move outside of map", sender);
		return;
	}
	if (objects.getWizard(wizard)->getPathSize() != 0) {
		std::cout << "movement ignored: wizard already moving!!!\n";
		return;
	}
	if (!objects.getWizard(wizard)->getIsReachable(x, y)) {
		invalidMessage(message, "location is not reachable!", sender);
		return;
	}
	objects.getWizard(wizard)->setPath(x, y);
}

// Message senders =============================================================
void Client::sendDisconnect(const char* reason) {
	if (strlen(reason) > 64) {
		std::cout << "Error: Disconnect reason too long\n";
		return;
	}
	char message[128];
	snprintf(message, 128, "DISCONNECT|%s", reason);
	sendMessage(message);
}

void Client::sendChat(const char* chat) {
	if (strlen(chat) > 256) {
		std::cout << "Error: Chat too long\n";
		return;
	}
	char message[512];
	snprintf(message, 512, "CHAT|%s", chat);
	sendMessage(message);
}

void Client::sendMove(const unsigned int x, const unsigned int y) {
	char message[128];
	snprintf(message, 128, "MOVE|%u|%u", x, y);
	sendMessage(message);
}

